package org.dftproject.genesis.core.collections;

import java.text.ParseException;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.analysis.SimpleAnalyzer;
import org.apache.lucene.document.DateTools;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.Hit;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TermQuery;
import org.eclipse.core.runtime.Platform;

public class CollectionManager {

	private static final Log log = LogFactory.getLog(CollectionManager.class);

	private static CollectionManager singleton = new CollectionManager();

	private final List<ICollectionManagerListener> listeners = Collections.synchronizedList(new LinkedList<ICollectionManagerListener>());

	private CollectionManager() {
		IndexWriter writer = null;
		try {
			writer = new IndexWriter(getCollectionIndexPath(), new SimpleAnalyzer());
		} catch (Exception e) {
			log.error("Failed to open collection index", e);
		} finally {
			try {
				if (writer != null)
					writer.close();
			} catch (Exception e) {
			}
		}
	}

	public static CollectionManager getDefault() {
		return singleton;
	}

	public String getCollectionIndexPath() {
		return Platform.getInstanceLocation().getURL().getPath().substring(1) + ".metadata/.collections";
	}
	
	public Collection getCollection(String id) {
		IndexSearcher searcher = null;
		try {
			searcher = new IndexSearcher(getCollectionIndexPath());

			Hits hits = searcher.search(new TermQuery(new Term("id", id)));
			if (hits.length() > 0)
				return createCollection(hits.doc(0));
		} catch (Exception e) {
			log.error("Failed to get collection '" + id + "'", e);
		} finally {
			try {
				if (searcher != null)
					searcher.close();
			} catch (Exception e) {
			}
		}
		
		return null;
	}

	/**
	 * Adds a collection to the database and returns the collection. If the
	 * collection already exists, the existing collection will be returned
	 * instead. Returns null if it cannot add the collection.
	 * @param collection a collection
	 * @return the new collection, the old one if it already exists, or null on
	 *         failure
	 */
	public Collection addCollection(Collection collection) {
		Collection existing = getCollection(collection.getId());
		if (existing != null)
			return existing;
		
		IndexWriter writer = null;
		try {
			writer = new IndexWriter(getCollectionIndexPath(), new SimpleAnalyzer());

			Document doc = new Document();
			doc.add(new Field("id", collection.getId(), Field.Store.COMPRESS, Field.Index.UN_TOKENIZED));
			doc.add(new Field("name", collection.getName(), Field.Store.COMPRESS, Field.Index.UN_TOKENIZED));
			doc.add(new Field("description", collection.getDescription(), Field.Store.COMPRESS, Field.Index.NO));
			doc.add(new Field("type", collection.getType(), Field.Store.COMPRESS, Field.Index.NO));
			doc.add(new Field("data", collection.getData(), Field.Store.COMPRESS, Field.Index.NO));
			doc.add(new Field("lastAccessed", DateTools.dateToString(collection.getLastAccessed(), DateTools.Resolution.SECOND), Field.Store.COMPRESS, Field.Index.UN_TOKENIZED));
			writer.addDocument(doc);
		} catch (Exception e) {
			log.error("Failed to add collection " + collection, e);
			return null;
		} finally {
			try {
				if (writer != null)
					writer.close();
			} catch (Exception e) {
			}
		}
		
		notifyCollectionAdded(collection);
		return collection;
	}

	public void removeCollection(Collection collection) {
		boolean success = false;
		
		IndexWriter writer = null;
		try {
			writer = new IndexWriter(getCollectionIndexPath(), new SimpleAnalyzer());
			writer.deleteDocuments(new Term("id", collection.getId()));
			success = true;
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			if (writer != null) {
				try {
					writer.close();
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
		
		if (success)
			notifyCollectionRemoved(collection);
	}

	public void touchCollection(Collection collection) {
		collection.setLastAccessed(new Date());
		updateCollection(collection);
	}

	public void updateCollection(Collection collection) {
		boolean success = false;
		
		IndexWriter writer = null;
		try {
			writer = new IndexWriter(getCollectionIndexPath(), new SimpleAnalyzer());

			Document doc = new Document();
			doc.add(new Field("id", collection.getId(), Field.Store.COMPRESS, Field.Index.UN_TOKENIZED));
			doc.add(new Field("name", collection.getName(), Field.Store.COMPRESS, Field.Index.UN_TOKENIZED));
			doc.add(new Field("description", collection.getDescription(), Field.Store.COMPRESS, Field.Index.NO));
			doc.add(new Field("type", collection.getType(), Field.Store.COMPRESS, Field.Index.UN_TOKENIZED));
			doc.add(new Field("data", collection.getData(), Field.Store.COMPRESS, Field.Index.NO));
			doc.add(new Field("lastAccessed", DateTools.dateToString(collection.getLastAccessed(), DateTools.Resolution.SECOND), Field.Store.COMPRESS, Field.Index.UN_TOKENIZED));
			writer.updateDocument(new Term("id", collection.getId()), doc);
			
			success = true;
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			if (writer != null) {
				try {
					writer.close();
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
		
		if (success)
			notifyCollectionUpdated(collection);
	}

	public Set<Collection> getCollections() {
		Set<Collection> collections = new HashSet<Collection>();

		IndexSearcher searcher = null;
		try {
			searcher = new IndexSearcher(getCollectionIndexPath());

			Hits hits = searcher.search(new MatchAllDocsQuery());
			@SuppressWarnings("unchecked")
			Iterator<Hit> iter = hits.iterator();
			while (iter.hasNext())
				collections.add(createCollection(iter.next().getDocument()));
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			if (searcher != null) {
				try {
					searcher.close();
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}

		return collections;
	}

	public List<Collection> getRecentCollections(int max) {
		List<Collection> collections = new LinkedList<Collection>();

		IndexSearcher searcher = null;
		try {
			searcher = new IndexSearcher(getCollectionIndexPath());

			Hits hits = searcher.search(new MatchAllDocsQuery(), new Sort(new SortField[] { new SortField("lastAccessed", true), new SortField("name") }));
			int n = (max == 0 ? hits.length() : Math.min(max, hits.length()));
			for (int i = 0; i < n; i++)
				collections.add(createCollection(hits.doc(i)));
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			if (searcher != null) {
				try {
					searcher.close();
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}

		return collections;
	}

	public Set<Collection> getCollections(String type) {
		Set<Collection> collections = new HashSet<Collection>();

		IndexSearcher searcher = null;
		try {
			searcher = new IndexSearcher(getCollectionIndexPath());

			Hits hits = searcher.search(new TermQuery(new Term("type", type)));
			@SuppressWarnings("unchecked")
			Iterator<Hit> iter = hits.iterator();
			while (iter.hasNext())
				collections.add(createCollection(iter.next().getDocument()));
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			if (searcher != null) {
				try {
					searcher.close();
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}

		return collections;
	}

	protected Collection createCollection(Document doc) throws ParseException {
		String lastAccessed = doc.get("lastAccessed");
		return new Collection(doc.get("id"), doc.get("name"), doc.get("description"), doc.get("type"), doc.get("data"), lastAccessed != null ? DateTools.stringToDate(lastAccessed) : null);
	}

	public void addListener(ICollectionManagerListener listener) {
		listeners.add(listener);
	}

	public void removeListener(ICollectionManagerListener listener) {
		listeners.remove(listener);
	}

	protected void notifyCollectionAdded(Collection collection) {
		for (ICollectionManagerListener listener : listeners)
			listener.collectionAdded(collection);
	}

	protected void notifyCollectionUpdated(Collection collection) {
		for (ICollectionManagerListener listener : listeners)
			listener.collectionUpdated(collection);
	}

	protected void notifyCollectionRemoved(Collection collection) {
		for (ICollectionManagerListener listener : listeners)
			listener.collectionRemoved(collection);
	}

}
